查看原文
其他

Spring框架中的设计模式(二)

2017-08-30 知秋 程序猿DD

在 上一篇 中我们在Spring中所谈到的设计模式涉及到了创建模式三剑客和1个行为模式(解释器模式)。这次我们会将眼光更多地关注在具有结构性和行为性的设计模式上。

在这篇文章中,我们将看到每个类型的两种模式。首先将关注类型是的结构设计模式。它将包含代理和复合。下一个将介绍行为模式:策略和模板方法。

代理模式

面向对象编程(OOP)可能是编程中最流行的概念。然而,Spring引入了另一种编码规范,面向切面编程(AOP)。为了简化定义,AOP是面向系统特定点的一种编程,如:异常抛出,特定类别方法的执行等.AOP允许在执行这些特定点之前或之后执行补充动作。如何实现这种操作?它可以通过监听器(listeners)进行。但在这种情况下,我们应该在只要可能存在调用的地方都需要定义监听器来进行监听(比如在一个方法的开始的地方)。这就是为什么Spring不采用这个idea。相反,Spring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式

代理就像对象的镜像一样。也正因为如此,代理对象不仅可以覆盖真实对象,还可以扩展其功能。因此,对于只能在屏幕上打印一些文本的对象,我们可以添加另一个对象来过滤显示单词。可以通过代理来定义第二个对象的调用。代理是封装真实对象的对象。例如,如果您尝试调用Waiter bean,那么您将调用该Bean的代理,其行为方式完全相同。

代理设计模式的一个很好的例子是org.springframework.aop.framework.ProxyFactoryBean。该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的 FactoryBean接口。此方法用于将需求 Bean的实例返回给 bean factory。在这种情况下,它不是返回的实例,而是 AOP代理。在执行代理对象的方法之前,可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已,而不是装饰模式什么也不做就加了件衣服,其他还得由你来全权完成)。

ProxyFactory的一个例子是:

  1. public class TestProxyAop {

  2.  @Test

  3.  public void test() {

  4.    ProxyFactory factory = new ProxyFactory(new House());

  5.    factory.addInterface(Construction.class);

  6.    factory.addAdvice(new BeforeConstructAdvice());

  7.    factory.setExposeProxy(true);

  8.    Construction construction = (Construction) factory.getProxy();

  9.    construction.construct();

  10.    assertTrue("Construction is illegal. "

  11.      + "Supervisor didn't give a permission to build "

  12.      + "the house", construction.isPermitted());

  13.  }

  14. }

  15. interface Construction {

  16.  public void construct();

  17.  public void givePermission();

  18.  public boolean isPermitted();

  19. }

  20. class House implements Construction{

  21.  private boolean permitted = false;

  22.  @Override

  23.  public boolean isPermitted() {

  24.    return this.permitted;

  25.  }

  26.  @Override

  27.  public void construct() {

  28.    System.out.println("I'm constructing a house");

  29.  }

  30.  @Override

  31.  public void givePermission() {

  32.    System.out.println("Permission is given to construct a simple house");

  33.    this.permitted = true;

  34.  }

  35. }

  36. class BeforeConstructAdvice implements MethodBeforeAdvice {

  37.  @Override

  38.  public void before(Method method, Object[] arguments, Object target) throws Throwable {

  39.    if (method.getName().equals("construct")) {

  40.      ((Construction) target).givePermission();

  41.    }

  42.  }

  43. }

这个测试应该通过,因为我们不直接在House实例上操作,而是代理它。代理调用第一个 BeforeConstructAdvicebefore方法(指向在执行目标方法之前执行,在我们的例子中为 construct())通过它,给出了一个“权限”来构造对象的字段(house)。代理层提供了一个额外新功能,因为它可以简单地分配给另一个对象。要做到这一点,我们只能在before方法之前修改过滤器。

复合模式

另一种结构模式是复合模式。在关于Spring中设计模式的第一篇文章中,我们使用构建器来构造复杂对象。另一种实现方法是使用复合模式。这种模式是基于具有共同行为的多个对象的存在,用于构建更大的对象。较大的对象仍然具有与最小对象相同的特征。那么用它来定义相同的行为。

复合对象的非Spring示例可以是一个写入HTML的文本对象,由包含span或em标签的段落组成:

  1. public class CompositeTest {

  2.  @Test

  3.  public void test() {

  4.    TextTagComposite composite = new PTag();

  5.    composite.addTag(new SpanTag());

  6.    composite.addTag(new EmTag());

  7.    // sample client code

  8.    composite.startWrite();

  9.    for (TextTag leaf : composite.getTags()) {

  10.      leaf.startWrite();

  11.      leaf.endWrite();

  12.    }

  13.    composite.endWrite();

  14.    assertTrue("Composite should contain 2 tags but it contains "+composite.getTags().size(), composite.getTags().size() == 2);

  15.  }

  16. }

  17. interface TextTag {

  18.  public void startWrite();

  19.  public void endWrite();

  20. }

  21. interface TextTagComposite extends TextTag {

  22.  public List<TextTag> getTags();

  23.  public void addTag(TextTag tag);

  24. }

  25. class PTag implements TextTagComposite {

  26.  private List<TextTag> tags = new ArrayList<TextTag>();

  27.  @Override

  28.  public void startWrite() {

  29.    System.out.println("<p>");

  30.  }

  31.  @Override

  32.  public void endWrite() {

  33.    System.out.println("</p>");

  34.  }

  35.  @Override

  36.  public List<TextTag> getTags() {

  37.    return tags;

  38.  }

  39.  @Override

  40.  public void addTag(TextTag tag) {

  41.    tags.add(tag);

  42.  }

  43. }

  44. class SpanTag implements TextTag {

  45.  @Override

  46.  public void startWrite() {

  47.    System.out.println("<span>");

  48.  }

  49.  @Override

  50.  public void endWrite() {

  51.    System.out.println("</span>");

  52.  }

  53. }

  54. class EmTag implements TextTag {

  55.  @Override

  56.  public void startWrite() {

  57.    System.out.println("<em>");

  58.  }

  59.  @Override

  60.  public void endWrite() {

  61.    System.out.println("</em>");

  62.  }

  63. }

在这种情况下,可以看到一个复合对象。我们可以区分复合与非复合对象,因为第一个可以容纳一个或多个非复合对象( PTag类中的 privateListtags字段)。非复合对象称为叶子。 TextTag接口被称为组件,因为它为两个对象类型提供了共同的行为规范(有点像 Linux文件管理系统的有共同点的文件放在一个文件夹下进行管理,其实就是节点管理)。

Spring世界中,我们检索复合对象的概念是org.springframework.beans.BeanMetadataElement接口,用于配置 bean对象。它是所有继承对象的基本界面。现在,在一方面,我们有一个叶子,由org.springframework.beans.factory.parsing.BeanComponentDefinition表示,另一边是复合org.springframework.beans.factory.parsing.CompositeComponentDefinitionCompositeComponentDefinition类似于组件,因为它包含addNestedComponent(ComponentDefinition component)方法,它允许将叶添加到私有final列表中 nestedComponents。您可以看到,由于此列表, BeanComponentDefinitionCompositeComponentDefinition的组件是org.springframework.beans.factory.parsing.ComponentDefinition

策略模式

本文描述的第三个概念是策略设计模式。策略定义了通过不同方式完成相同事情的几个对象。完成任务的方式取决于采用的策略。举个例子说明,我们可以去一个国家。我们可以乘公共汽车,飞机,船甚至汽车去那里。所有这些方法将把我们运送到目的地国家。但是,我们将通过检查我们的银行帐户来选择最适应的方式。如果我们有很多钱,我们将采取最快的方式(可能是私人飞行)。如果我们没有足够的话,我们会采取最慢的(公车,汽车)。该银行账户作为确定适应策略的因素。

Spring在org.springframework.web.servlet.mvc.multiaction.MethodNameResolver类(过时,但不影响拿来研究)中使用策略设计模式。它是 MultiActionController(同样过时)的参数化实现。在开始解释策略之前,我们需要了解MultiActionController的实用性。这个类允许同一个类处理几种类型的请求。作为Spring中的每个控制器,MultiActionController执行方法来响应提供的请求。策略用于检测应使用哪种方法。解析过程在MethodNameResolver实现中实现,例如在同一个包中的ParameterMethodNameResolver中。方法可以通过多个条件解决:属性映射,HTTP请求参数或URL路径。

  1. @Override

  2. public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {

  3.  String methodName = null;

  4.  // Check parameter names where the very existence of each parameter

  5.  // means that a method of the same name should be invoked, if any.

  6.  if (this.methodParamNames != null) {

  7.    for (String candidate : this.methodParamNames) {

  8.      if (WebUtils.hasSubmitParameter(request, candidate)) {

  9.        methodName = candidate;

  10.        if (logger.isDebugEnabled()) {

  11.          logger.debug("Determined handler method '" + methodName +

  12.            "' based on existence of explicit request parameter of same name");

  13.        }

  14.        break;

  15.      }

  16.    }

  17.  }

  18.  // Check parameter whose value identifies the method to invoke, if any.

  19.  if (methodName == null && this.paramName != null) {

  20.    methodName = request.getParameter(this.paramName);

  21.    if (methodName != null) {

  22.      if (logger.isDebugEnabled()) {

  23.        logger.debug("Determined handler method '" + methodName +

  24.          "' based on value of request parameter '" + this.paramName + "'");

  25.      }

  26.    }

  27.  }

  28.  if (methodName != null && this.logicalMappings != null) {

  29.    // Resolve logical name into real method name, if appropriate.

  30.    String originalName = methodName;

  31.    methodName = this.logicalMappings.getProperty(methodName, methodName);

  32.    if (logger.isDebugEnabled()) {

  33.      logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'");

  34.    }

  35.  }

  36.  if (methodName != null && !StringUtils.hasText(methodName)) {

  37.    if (logger.isDebugEnabled()) {

  38.      logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found");

  39.    }

  40.    methodName = null;

  41.  }

  42.  if (methodName == null) {

  43.    if (this.defaultMethodName != null) {

  44.      // No specific method resolved: use default method.

  45.      methodName = this.defaultMethodName;

  46.      if (logger.isDebugEnabled()) {

  47.        logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'");

  48.      }

  49.    }

  50.    else {

  51.      // If resolution failed completely, throw an exception.

  52.      throw new NoSuchRequestHandlingMethodException(request);

  53.    }

  54.  }

  55.  return methodName;

  56. }

正如我们在前面的代码中可以看到的,方法的名称通过提供的参数映射,URL中的预定义属性或参数存在来解决(默认情况下,该参数的名称是action)。

模板模式

本文提出的最后一个设计模式是模板方法。此模式定义了类行为的骨架,并将子步骤的某些步骤的延迟执行(具体就是下面例子中一个方法放在另一个方法中,只有调用另一方方法的时候这个方法才会执行,而且还可能会在其他行为方法之后按顺序执行)。其中写了一种方法(下面例子中的construct()),注意定义为final,起着同步器的角色。它以给定的顺序执行由子类定义的方法。在现实世界中,我们可以将模板方法与房屋建设进行比较。独立于建造房屋的公司,我们需要从建立基础开始,只有在我们完成之后才能做其他的工作。这个执行逻辑将被保存在一个我们不能改变的方法中。例如基础建设或刷墙会被作为一个模板方法中的方法,具体到建筑房屋的公司。我们可以在给定的例子中看到它:

  1. public class TemplateMethod {

  2.  public static void main(String[] args) {

  3.    HouseAbstract house = new SeaHouse();

  4.    house.construct();

  5.  }

  6. }

  7. abstract class HouseAbstract {

  8.  protected abstract void constructFoundations();

  9.  protected abstract void constructWall();

  10.  // template method

  11.  public final void construct() {

  12.    constructFoundations();

  13.    constructWall();

  14.  }

  15. }

  16. class EcologicalHouse extends HouseAbstract {

  17.  @Override

  18.  protected void constructFoundations() {

  19.    System.out.println("Making foundations with wood");

  20.  }

  21.  @Override

  22.  protected void constructWall() {

  23.    System.out.println("Making wall with wood");

  24.  }

  25. }

  26. class SeaHouse extends HouseAbstract {

  27.  @Override

  28.  protected void constructFoundations() {

  29.    System.out.println("Constructing very strong foundations");

  30.  }

  31.  @Override

  32.  protected void constructWall() {

  33.    System.out.println("Constructing very strong wall");

  34.  }

  35. }

该代码应该输出:

  1. Constructing very strong foundations

  2. Constructing very strong wall

Spring在org.springframework.context.support.AbstractApplicationContext类中使用模板方法。他们不是一个模板方法(在我们的例子中是construct ),而是多个。例如,getsFreshBeanFactory返回内部 bean工厂的新版本,调用两个抽象方法: refreshBeanFactory(刷新工厂bean)和 getBeanFactory(以获取更新的工厂bean)。这个方法和其他一些方法一样,用在public void refresh()中,抛出构造应用程序上下文的BeansException,IllegalStateException方法(这里会在后面Spring中与应用程序上下文分析中再次提到)。

我们可以从同一个包中的GenericApplicationContext找到一些通过模板方法所实现的抽象方法的实现的例子(说的有点拗口,多读几遍就好):

  1. /**

  2.  * Do nothing: We hold a single internal BeanFactory and rely on callers

  3.  * to register beans through our public methods (or the BeanFactory's).

  4.  * @see #registerBeanDefinition

  5.  */

  6. @Override

  7. protected final void refreshBeanFactory() throws IllegalStateException {

  8.  if (this.refreshed) {

  9.    throw new IllegalStateException(

  10.      "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");

  11.  }

  12.  this.beanFactory.setSerializationId(getId());

  13.  this.refreshed = true;

  14. }

  15. @Override

  16. protected void cancelRefresh(BeansException ex) {

  17.  this.beanFactory.setSerializationId(null);

  18.  super.cancelRefresh(ex);

  19. }

  20. /**

  21.  * Not much to do: We hold a single internal BeanFactory that will never

  22.  * get released.

  23.  */

  24. @Override

  25. protected final void closeBeanFactory() {

  26.  this.beanFactory.setSerializationId(null);

  27. }

  28. /**

  29.  * Return the single internal BeanFactory held by this context

  30.  * (as ConfigurableListableBeanFactory).

  31.  */

  32. @Override

  33. public final ConfigurableListableBeanFactory getBeanFactory() {

  34.  return this.beanFactory;

  35. }

  36. /**

  37.  * Return the underlying bean factory of this context,

  38.  * available for registering bean definitions.

  39.  * <p><b>NOTE:</b> You need to call {@link #refresh()} to initialize the

  40.  * bean factory and its contained beans with application context semantics

  41.  * (autodetecting BeanFactoryPostProcessors, etc).

  42.  * @return the internal bean factory (as DefaultListableBeanFactory)

  43.  */

  44. public final DefaultListableBeanFactory getDefaultListableBeanFactory() {

  45.  return this.beanFactory;

  46. }

经过上面这些可以让我们发现Spring如何通过使用行为和结构设计模式来更好地组织上下文(模板方法),并通过相应策略来解决执行方法。它使用两种结构设计模式,通过代理模式来简化AOP部分并通过复合模式来构造复杂对象。

推荐阅读

长按指纹

一键关注

点击“阅读原文”

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存